/*
 * Copyright 2021-2022 Open Kunlun Technology <https://www.openkunlun.io>
 */

package io.openkunlun.scaladsl.client

import akka.actor.ActorSystem
import io.openkunlun.scaladsl.model.{ StateConsistency, StateItem }
import io.openkunlun.scaladsl.serialization._

import scala.concurrent.{ ExecutionContext, Future }

/**
 * @author ericxin.
 */
object DaprAction extends DaprAction {
  val GRPC_TRACE_BIN_KEY = "grpc-trace-bin"
  val TRACE_PARENT_KEY = "traceparent"
  val TRACE_STATE_KEY = "tracestate"
  val DARP_API_TOKEN = "dapr-api-token"
}
trait DaprAction {

  def apply(system: ActorSystem): DaprActionBuilder = DaprActionBuilder.Impl(system, JsonSerialization(system))
  def apply(system: ActorSystem, serialization: DaprObjectSerialization): DaprActionBuilder = DaprActionBuilder.Impl(system, serialization)

  def withJsonSerialization(system: ActorSystem): DaprActionBuilder = {
    DaprActionBuilder.Impl(system, JsonSerialization(system))
  }
  def withJsonDelegateSerialization(system: ActorSystem): DaprActionBuilder = {
    DaprActionBuilder.Impl(system, JsonDelegateSerialization(system))
  }

  def withAkkaSerialization(system: ActorSystem): DaprActionBuilder = {
    DaprActionBuilder.Impl(system, AkkaSerialization(system))
  }
  def withAkkaDelegateSerialization(system: ActorSystem): DaprActionBuilder = {
    DaprActionBuilder.Impl(system, AkkaDelegateSerialization(system))
  }
}

final case class InvokeMethodAction(
  id: String,
  method: String,
  data: AnyRef,
  contentType: Option[String] = None
)

final case class InvokeBindingAction(
  name: String,
  data: AnyRef,
  operation: Option[String] = None,
  metadata: Map[String, String] = Map.empty
)

final case class GetStateAction(
  storeName: String,
  key: String,
  consistency: String = StateConsistency.ConsistencyStrong,
  metadata: Map[String, String] = Map.empty
)

final case class GetBulkStateAction(
  storeName: String,
  keys: Seq[String],
  parallelism: Int,
  metadata: Map[String, String] = Map.empty
)

final case class DeleteStateAction(
  storeName: String,
  key: String,
  etag: Option[String] = None,
  metadata: Map[String, String] = Map.empty
)

final case class SetStateAction(
  storeName: String,
  states: Seq[StateItem] = Seq.empty
)

final case class ExecuteStateAction(
  storeName: String,
  operations: Seq[TransactionalStateOperation],
  metadata: Map[String, String] = Map.empty
)

final case class TransactionalStateOperation(
  operation: String,
  state: StateItem
)

final case class GetSecretAction(
  storeName: String,
  key: String,
  metadata: Map[String, String] = Map.empty
)

final case class GetBulkSecretAction(
  storeName: String,
  metadata: Map[String, String] = Map.empty
)

final case class SetMetadataAction(
  key: String,
  value: String
)

trait DaprActionBuilder {
  def withToken(token: String): DaprActionBuilder
  def withTraceId(traceId: String): DaprActionBuilder
  def withHeader(key: String, value: String): DaprActionBuilder
  def withHeaders(headers: Map[String, String]): DaprActionBuilder
  def invokeMethod(id: String, method: String, data: AnyRef): InvokeMethodActionBuilder
  def invokeBinding(name: String, data: AnyRef): InvokeBindingActionBuilder
}
private[client] object DaprActionBuilder {

  final case class Impl(
    system: ActorSystem,
    serialization: DaprObjectSerialization,
    headers: Map[String, String] = Map.empty) extends DaprActionBuilder {

    override def withToken(token: String): DaprActionBuilder = {
      copy(headers = headers + (DaprAction.DARP_API_TOKEN -> token))
    }

    override def withTraceId(traceId: String): DaprActionBuilder = {
      copy(headers = headers + (DaprAction.TRACE_PARENT_KEY -> traceId))
    }

    override def withHeader(key: String, value: String): DaprActionBuilder = {
      copy(headers = headers + (key -> value))
    }

    override def withHeaders(headers: Map[String, String]): DaprActionBuilder = {
      copy(headers = headers ++ headers)
    }

    override def invokeMethod(id: String, method: String, data: AnyRef): InvokeMethodActionBuilder = {
      InvokeMethodActionBuilder.Impl(system, serialization, headers, id, method, data)
    }

    override def invokeBinding(name: String, data: AnyRef): InvokeBindingActionBuilder = {
      InvokeBindingActionBuilder.Impl(system, serialization, headers, name, data)
    }
  }

}

trait InvokeMethodActionBuilder {
  def withContentType(contentType: String): InvokeMethodActionBuilder
  def execute[T: Manifest](implicit ec: ExecutionContext): Future[T]
}
private[client] object InvokeMethodActionBuilder {

  final case class Impl(
    system: ActorSystem,
    serialization: DaprObjectSerialization,
    headers: Map[String, String],
    id: String,
    method: String,
    data: AnyRef,
    contentType: Option[String] = None) extends InvokeMethodActionBuilder {

    override def withContentType(contentType: String): InvokeMethodActionBuilder = {
      copy(contentType = Some(contentType))
    }

    override def execute[T: Manifest](implicit ec: ExecutionContext): Future[T] = {
      implicit val ser = serialization
      DaprClient(system).invokeMethod[T](InvokeMethodAction(id, method, data, contentType), headers)
    }
  }

}

trait InvokeBindingActionBuilder {
  def withOperation(operation: String): InvokeBindingActionBuilder
  def withMetadata(metadata: Map[String, String]): InvokeBindingActionBuilder
  def execute[T: Manifest](implicit ec: ExecutionContext): Future[T]
}
private[client] object InvokeBindingActionBuilder {

  final case class Impl(
    system: ActorSystem,
    serialization: DaprObjectSerialization,
    headers: Map[String, String],
    name: String,
    data: AnyRef,
    operation: Option[String] = None,
    metadata: Map[String, String] = Map.empty) extends InvokeBindingActionBuilder {

    override def withOperation(operation: String): InvokeBindingActionBuilder = {
      copy(operation = Some(operation))
    }

    override def withMetadata(metadata: Map[String, String]): InvokeBindingActionBuilder = {
      copy(metadata = metadata)
    }

    override def execute[T: Manifest](implicit ec: ExecutionContext): Future[T] = {
      implicit val ser = serialization
      DaprClient(system).invokeBinding[T](InvokeBindingAction(name, data, operation, metadata), headers)
    }
  }

}
